//
//  dma.c
//  NMEA
//
//  Created by Hans Johnson on 2/11/2014.
//
//  This file includes the thread that does the DMA scheduling, and the various
//  associated utility functions

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "avr_compiler.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "semphr.h"
#include "dma.h"

// The DMA Request Queue
volatile xQueueHandle dmaReqQueue;

// task handles associated with currently active DMA transfers
xTaskHandle callingTaskHandles[4];

// Counting mutex to manage DMA channel availability
xSemaphoreHandle dmaUnitSem;

// Main task that does the DMA Scheduling
void dmaTask(void *pvParameters)
{
    // Initialize the DMA unit, turn DMA.CTRL[7] to 1.
    DMA.CTRL |= (1<<7);
    
    // Initialize the DMA request queue
    dmaReqQueue = xQueueCreate(DMA_QUEUES,sizeof(DMA_REQ_t));
    
    // Generic itterator
    uint8_t i;
    
    // Temp variable
    uint8_t temp;
    
    // Mask variable
    uint8_t mask;
    
    // DMA Channel Index
    DMA_CH_t *dmaChannel = &DMA.CH0;
    
    // Mutex to detect availability of the DMA controllers
    dmaUnitSem = xSemaphoreCreateCounting(4, 4);
    
    // Buffer to hold the DMA Request
    DMA_REQ_t currReq;
    
    // Make sure we got the semaphore created
    if (dmaUnitSem != NULL)
    {
        // Go into our infinite loop
        while (1)
        {
            // Wait for a message to come in on the DMA queue
            xQueueReceive(dmaReqQueue, &currReq, portMAX_DELAY);
            
            // Wait for a DMA Queue to become available
            xSemaphoreTake(dmaUnitSem, portMAX_DELAY);
            // load the DMA status into a register
            temp = DMA.STATUS;
            // mask for finding the available channel
            mask = 0x11;
            // itterate over the four channels, until we find it
            for (i=0; i<4; i++)
            {
                // check to see if the channel is idle
                if ((temp & mask) == 0)
                {
                    // break out of the loop
                    break;
                }
                else
                {
                    mask = mask<<1;
                }
            }
            
            // Save the task handle
            callingTaskHandles[i] = currReq.reqTask;
            
            // i now contains the first available DMA channel
            // Load the addresses into the destination field of the DMA channel
            (dmaChannel+i)->DESTADDR0 = (uint8_t)((uint16_t)currReq.dmaDest & 0xff);
            (dmaChannel+i)->DESTADDR1 = ((uint16_t)(currReq.dmaDest)>>8);
            (dmaChannel+i)->DESTADDR2 = 0;
            
            // Load the addreses into the source field fo the DMA channel
            (dmaChannel+i)->SRCADDR0 = (uint8_t)((uint16_t)currReq.dmaSrc & 0xff);
            (dmaChannel+i)->SRCADDR1 = ((uint16_t)(currReq.dmaSrc)>>8);
            (dmaChannel+i)->SRCADDR2 = 0;
            
            // Set the trigger source to 00 (ie software triggers only
            (dmaChannel+i)->TRIGSRC = DMA_CH_TRIGSRC_OFF_gc;
            
            // Address Control as part of the management
            (dmaChannel+i)->ADDRCTRL = currReq.CTRL;
            
            // Load the byte count
            (dmaChannel+i)->TRFCNT = currReq.count;
            
            // Enable the transfer complete interrupt
            (dmaChannel+i)->CTRLB |= DMA_CH_TRNINTLVL_MED_gc;
            
            // Configure the DMA unit - Set burst size to 4
            (dmaChannel+i)->CTRLA = DMA_CH_BURSTLEN_4BYTE_gc;
            
            // Enable the DMA channel
            (dmaChannel+i)->CTRLA |= DMA_CH_ENABLE_bm;
            
            // Trigger the DMA transfer
            (dmaChannel+i)->CTRLA |= DMA_CH_TRFREQ_bm;
        }
    }
}

/* DMA Channel 0 Interrupt */
ISR(DMA_CH0_vect)
{
    // Check to make sure this was actually a complete transfer
    if (DMA.CH0.CTRLB & DMA_CH_TRNIF_bm)
    {
        // Clear the flag
        DMA.CH0.CTRLB &= ~DMA_CH_TRNIF_bm;
        
        // Disable the interrupt
        DMA.CH0.CTRLB &= ~DMA_CH_TRNINTLVL_gm;
        
        // Give back a mutex
        xSemaphoreGiveFromISR(dmaUnitSem, NULL);
        
        // Restart the task that made the request
        xTaskResumeFromISR(callingTaskHandles[0]);
        
        // Disable the DMA channel
        DMA.CH0.CTRLA &= ~(DMA_CH_ENABLE_bm);
        
        // Reset the the DMA channel
        DMA.CH0.CTRLA |= DMA_CH_RESET_bm;
    };
}

/* DMA Channel 1 Interrupt */
ISR(DMA_CH1_vect)
{
    // Check to make sure this was actually a complete transfer
    if (DMA.CH1.CTRLB & DMA_CH_TRNIF_bm)
    {
        // Clear the flag
        DMA.CH1.CTRLB &= ~DMA_CH_TRNIF_bm;
        
        // Disable the interrupt
        DMA.CH1.CTRLB &= ~DMA_CH_TRNINTLVL_gm;
        
        // Give back a mutex
        xSemaphoreGiveFromISR(dmaUnitSem, NULL);
        
        // Restart the task that made the request
        xTaskResumeFromISR(callingTaskHandles[1]);
        
        // Disable the DMA channel
        DMA.CH1.CTRLA &= ~(DMA_CH_ENABLE_bm);
        
        // Reset the the DMA channel
        DMA.CH1.CTRLA |= DMA_CH_RESET_bm;
    };
}

/* DMA Channel 2 Interrupt */
ISR(DMA_CH2_vect)
{
    // Check to make sure this was actually a complete transfer
    if (DMA.CH2.CTRLB & DMA_CH_TRNIF_bm)
    {
        // Clear the flag
        DMA.CH2.CTRLB &= ~DMA_CH_TRNIF_bm;
        
        // Disable the interrupt
        DMA.CH2.CTRLB &= ~DMA_CH_TRNINTLVL_gm;
        
        // Give back a mutex
        xSemaphoreGiveFromISR(dmaUnitSem, NULL);
        
        // Restart the task that made the request
        xTaskResumeFromISR(callingTaskHandles[2]);
        
        // Disable the DMA channel
        DMA.CH2.CTRLA &= ~(DMA_CH_ENABLE_bm);
        
        // Reset the the DMA channel
        DMA.CH2.CTRLA |= DMA_CH_RESET_bm;
    };
}

/* DMA Channel 3 Interrupt */
ISR(DMA_CH3_vect)
{
    // Check to make sure this was actually a complete transfer
    if (DMA.CH3.CTRLB & DMA_CH_TRNIF_bm)
    {
        // Clear the flag
        DMA.CH3.CTRLB &= ~DMA_CH_TRNIF_bm;
        
        // Disable the interrupt
        DMA.CH3.CTRLB &= ~DMA_CH_TRNINTLVL_gm;
        
        // Give back a mutex
        xSemaphoreGiveFromISR(dmaUnitSem, NULL);
        
        // Restart the task that made the request
        xTaskResumeFromISR(callingTaskHandles[3]);
        
        // Disable the DMA channel
        DMA.CH3.CTRLA &= ~(DMA_CH_ENABLE_bm);
        
        // Reset the the DMA channel
        DMA.CH3.CTRLA |= DMA_CH_RESET_bm;
    };
}
